﻿//------------------------------------------------------------------------------
// Copyright © FRA & FV 2014
// All rights reserved
//------------------------------------------------------------------------------
// Binary Serialization Custom Tool
//
// SVN revision information:
//   $Revision: 100 $
//------------------------------------------------------------------------------
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FRAFV.Binary.Serialization.CodeDom;

namespace FRAFV.Binary.Serialization
{
	/// <summary>
	/// public void ReadBinary(BinaryReader reader, long length)
	/// {
	/// 	Property1 = (Type1)reader.ReadType1();
	/// 	...
	/// 	if (Checked2)
	/// 		Property2 = (Type2)reader.ReadType2();
	/// 	...
	/// }
	/// </summary>
	internal sealed class ReadBinaryGenerator : ClassGenerator.MethodGenerator
	{
		public ReadBinaryGenerator(ClassParser cls)
			: base(cls) { }

		protected override ClassGenerator.MethodContext CreateMember(ClassParser cls)
		{
			//void ReadBinary(BinaryReader reader)
			var code = new CodeMemberMethod
			{
				Name = "ReadBinary"
			};
			code.Parameters.Add<BinaryReader>("reader");
			return new MethodContext(cls, code);
		}

		protected override void Init()
		{
			if (!context.Class.BinBlock.ReadMethod.IsNullEmpty())
			{
				//ReadMethod;
				Code.Snippet(context.Class.BinBlock.ReadMethod);
			}
			if (GenerateBaseCall)
			{
				//base.ReadBinary(reader);
				Code.BaseMethodInvoke("ReadBinary", "reader".AsArgument());
			}
			if (HasEncoding)
			{
				//var encoder = new BinaryReader(reader.BaseStream, Encoding);
				Code.Variable("encoder", context.Class.BinBlock.ResolveBinaryReaderType().AsType().CreateInvoke(
					"reader".AsArgument().Property("BaseStream"), GetEncoding()));
			}
			if (HasArrayCount)
			{
				//int count
				Code.Variable<int>("count");
			}
		}

		private sealed class MethodContext : ClassGenerator.MethodContext
		{
			public MethodContext(ClassParser cls, CodeMemberMethod member)
				: base(cls, member, member.Statements)
			{
				member.Attributes = MethodsAccess;
			}

			protected override string ResolveItemMethod()
			{
				return Class.BinBlock.ReadItemMethod;
			}
		}

		protected override ClassGenerator.PropertyGenerator CreateProperty(ClassParser.ClassProperty prop)
		{
			var context = new PropertyContext(this.context, prop);
			if (context.IsCustom)
			{
				return new PropertyCustomGenerator(context);
			}
			else if (context.IsItem)
			{
				return CreateItem(context);
			}
			else if (context.IsArrayItem)
			{
				return new PropertyArrayItemGenerator(context, CreateDataArray(context));
			}
			else
			{
				return new PropertyDataGenerator(context, CreateDataArray(context));
			}
		}

		private ClassGenerator.PropertyGenerator CreateDataArray(PropertyContext context)
		{
			if (!context.IsArray) return null;
			if (context.IsConditionArray)
				return new PropertyCollectionConditionGenerator(context);
			else if (context.CountHasStorage)
			{
				if (context.Property.IsCollection)
					return new PropertyCollectionGenerator(context);
				else
					return new PropertyArrayGenerator(context);
			}
			else
			{
				if (context.Property.IsCollection)
					return new PropertyCollectionCustomGenerator(context);
				else if (context.CountIsNumber)
					return new PropertyArrayNumGenerator(context);
				else
					return new PropertyArrayCustomGenerator(context);
			}
		}

		private ClassGenerator.PropertyItemGenerator CreateItem(PropertyContext context)
		{
			if (this.HasItemMethod)
				switch (this.context.Class.BinBlock.ItemOrder)
				{
					case BinItemOrder.Sequence:
						return new PropertySequenceItemGenerator(context, CreateItemArray(context)); //single&repeat
					case BinItemOrder.Choice:
						if (!context.IsRepeatItem)
							return new PropertyChoiceItemGenerator(context, CreateItemArray(context));
						break;
					case BinItemOrder.Any:
						return new PropertyAnyItemGenerator(context, CreateItemArray(context)); //single&repeat
				}
			else
				switch (this.context.Class.BinBlock.ItemOrder)
				{
					case BinItemOrder.Sequence:
						return new PropertySequenceSampleGenerator(context, CreateItemArray(context)); //single&repeat
					case BinItemOrder.Choice:
						if (!context.IsRepeatItem)
							return new PropertyChoiceSampleGenerator(context, CreateItemArray(context));
						break;
				}
			throw new NotSupportedException();
		}

		private ClassGenerator.PropertyGenerator CreateItemArray(PropertyContext context)
		{
			return null;
		}

		private sealed class PropertyContext : ClassGenerator.PropertyContext
		{
			public PropertyContext(ClassGenerator.MethodContext context, ClassParser.ClassProperty prop)
				: base(context, prop) { }

			protected override string ResolveCountMethod(out Type arg)
			{
				return Property.BinArray.CountFormat.ResolveRead(out arg);
			}
		}

		private class PropertyDataGenerator : ClassGenerator.PropertyDataGenerator
		{
			public PropertyDataGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override ClassGenerator.FormatGenerator CreateFormat(BinDataAttribute binData)
			{
				var context = new FormatContext(this.context, binData);
				if (!context.Format.HasLength())
					return new FormatPrimitiveGenerator(context);
				else if (context.LengthHasStorage)
					return new FormatLongGenerator(context);
				else
					return new FormatLongCustomGenerator(context);
			}
		}

		//[BinCustom]
		private sealed class PropertyCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				Statements
					.Comment(Name)
					//Read_Property(reader, length);
					.Snippet(BinCustom.ReadMethod ?? "Read_" + Name + "(reader)");
				return null;
			}
		}

		//[BinArray(CountFormat)] Type[] Property
		//[BinArray(CountFormat, CountCustomMethod)] Type[] Property
		private sealed class PropertyArrayGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyArrayGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				var statements = Statements
					//if (true) {
					.Condition(true.AsExpression()).TrueStatements;

				var loop = statements
					//count = (int)reader.ReadCount();
					.Assign("count".AsVariable(), "reader".AsArgument().MethodInvoke(context.CountMethod).Cast<int>())
					//var buffer = new Type[count];
					.Variable("buffer", Type.CreateArray("count".AsVariable()))
					//for(int index = 0; index < count; index++)
					.ForCount("index", "count".AsVariable());

				//Property = buffer;
				statements.Assign(context.Property, "buffer".AsVariable().Cast(ArrayTypeName));
				return loop.Statements;
			}
		}

		//[BinArray(CountFormat)] Collection<Type> Property
		//[BinArray(CountFormat)] CollectionType Property
		//[BinArray(CountFormat, CountCustomMethod)] Collection<Type> Property
		//[BinArray(CountFormat, CountCustomMethod)] CollectionType Property
		private sealed class PropertyCollectionGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCollectionGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				return Statements
					//count = (int)reader.ReadCount();
					.Assign("count".AsVariable(), "reader".AsArgument().MethodInvoke(context.CountMethod).Cast<int>())
					//for(int index = 0; index < count; index++)
					.ForCount("index", "count".AsVariable()).Statements;
			}
		}

		//[BinArray(CountCustomMethod = "Number")] Type[] Property
		//[BinArray(CountCustomMethod = "Number")] ArrayType Property
		private sealed class PropertyArrayNumGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyArrayNumGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				var statements = Statements
					//if (true) {
					.Condition(true.AsExpression()).TrueStatements;

				var loop = statements
					//var buffer = new Type[N];
					.Variable("buffer", Type.CreateArray(CountNumber))
					//for(int index = 0; index < N; index++)
					.ForCount("index", CountNumber.AsExpression());

				//Property = (Type[])buffer;
				statements.Assign(context.Property, "buffer".AsVariable().Cast(ArrayTypeName));
				return loop.Statements;
			}
		}

		//[BinArray(CountCustomMethod)] Type[] Property
		//[BinArray(CountCustomMethod)] ArrayType Property
		private sealed class PropertyArrayCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyArrayCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				var statements = Statements
					//if (true) {
					.Condition(true.AsExpression()).TrueStatements;

				var loop = statements
					//count = (int)Count;
					.Assign("count".AsVariable(), CountCustomMethod.Cast<int>())
					//var buffer = new Type[count];
					.Variable("buffer", Type.CreateArray("count".AsVariable()))
					//for(int index = 0; index < count; index++)
					.ForCount("index", "count".AsVariable());

				//Property = buffer;
				statements.Assign(context.Property, "buffer".AsVariable().Cast(ArrayTypeName));
				return loop.Statements;
			}
		}

		//[BinArray(CountCustomMethod)] Collection<Type> Property
		private sealed class PropertyCollectionCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCollectionCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				return Statements
					//count = (int)Count;
					.Assign("count".AsVariable(), CountCustomMethod.Cast<int>())
					//for(int index = 0; index < count; index++)
					.ForCount("index", "count".AsVariable()).Statements;
			}
		}

		//[BinArray(ConditionCustomMethod)] Collection<Type> Property
		//[BinArray(ConditionCustomMethod)] CollectionType Property
		private sealed class PropertyCollectionConditionGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCollectionConditionGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				return Statements
					//for(int index = 0; Condition; index++)
					.For("index", ConditionCustomMethod).Statements;
			}
		}

		//[BinArray, BinArrayItem]
		private sealed class PropertyArrayItemGenerator : ClassGenerator.PropertyArrayItemGenerator
		{
			public PropertyArrayItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				Statements.Comment(Name);

				if (!HasCreateCustomMethod)
					//var item = new ItemType();
					Statements.Variable("item", Type.CreateInvoke());
				else
					//var item = Item_Create();
					Statements.Variable("item", CreateCustomMethod);

				if (!context.Property.IsCollection)
					//buffer[index] = item;
					Statements.Assign("buffer".AsVariable().Index("index"), "item".AsVariable());
				else
					//Property.Add(item);
					Statements.MethodInvoke(Name.AsField(), "Add", "item".AsVariable());

				//item.ReadBinary(reader);
				Statements.MethodInvoke("item".AsVariable(), "ReadBinary", "reader".AsVariable());
				return null;
			}
		}

		private abstract class PropertyItemGenerator : ClassGenerator.PropertyItemGenerator
		{
			protected PropertyItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected CodeExpression ItemCondition()
			{
				return BinItem.Select(item =>
				{
					//item is Type
					CodeExpression ic = "item".AsVariable().Is(item.ResolveItemType() ?? TypeName);
					if (!item.Condition.IsNullEmpty())
						//... && (Condition)
						ic = ic.And(item.Condition.AsSnippet());
					return ic;
				}).OrValues();
			}

			internal static void ResolveNextSimpleItem(CodeStatementCollection statements, BinBlockAttribute binBlock, string type)
			{
				statements
					//item = new Item();
					.Assign("item".AsVariable(), type.AsType().CreateInvoke())
					//item.ReadBinary(reader);
					.MethodInvoke("item".AsVariable(), "ReadBinary", "reader".AsVariable());
			}

			protected void ForEachSample(Action<BinItemAttribute> read)
			{
				var next = Statements;
				foreach (var item in BinItem)
				{
					if (!item.Condition.IsNullEmpty())
					{
						//if (Condition) {
						var cond = Statements.Condition(item.Condition.AsSnippet());
						NextStatements = cond.TrueStatements;
						next = cond.FalseStatements;
					}

					Statements.Comment(Name);
					read(item);

					//} else {
					NextStatements = next;
				}
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Sequence, xItemMethod)]
		private sealed class PropertySequenceItemGenerator : PropertyItemGenerator
		{
			public PropertySequenceItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			public override CodeStatementCollection Init(CodeStatementCollection code, IEnumerable<ClassGenerator.PropertyItemGenerator> props)
			{
				//var item = ReadItem(...);
				code.Variable("item", ItemMethod);
				return null;
			}

			protected override CodeStatementCollection GenerateData()
			{
				if (context.IsRepeatItem)
				{
					//while (item is Type) {
					Statements.While(ItemCondition()).Statements
						.Comment(Name)
						//Property.Add((Type)item);
						.MethodInvoke(Name.AsField(), "Add", "item".AsVariable().Cast(TypeName))
						//item = ReadItem(...);
						.Assign("item".AsVariable(), ItemMethod);
				}
				else
				{
					//if (item is Type) {
					var seq = Statements.Condition(ItemCondition());
					seq.TrueStatements
						.Comment(Name)
						//Property = (Type)item;
						.Assign(context.Property, "item".AsVariable().Cast(TypeName))
						//item = ReadItem(...);
						.Assign("item".AsVariable(), ItemMethod);
					if (context.IsRequiredItem)
					{
						//} else {
						seq.FalseStatements
							.Comment(Name)
							//Property1 = new Item1();
							.Assign(context.Property, RequiredItemType.CreateInvoke())
							//Property1.InitBinary();
							.MethodInvoke(Name.AsField(), "InitBinary");
					}
				}
				return null;
			}
		}

		//[BinBlock(ItemOrder = BinItemOrder.Choice, xItemMethod)]
		private class PropertyChoiceItemGenerator : PropertyItemGenerator
		{
			public PropertyChoiceItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			public override CodeStatementCollection Init(CodeStatementCollection code, IEnumerable<ClassGenerator.PropertyItemGenerator> props)
			{
				//var item = ReadItem(...);
				code.Variable("item", ItemMethod);
				return null;
			}

			protected override CodeStatementCollection GenerateData()
			{
				//if (item is Type) {
				var any = Statements.Condition(ItemCondition());
				any.TrueStatements
					.Comment(Name)
					//Property = (Type)item;
					.Assign(context.Property, "item".AsVariable().Cast(TypeName));
				//} else {
				return any.FalseStatements;
			}
		}

		//[BinBlock(ItemOrder = BinItemOrder.Any, xItemMethod)]
		private sealed class PropertyAnyItemGenerator : PropertyItemGenerator
		{
			public PropertyAnyItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			public override CodeStatementCollection Init(CodeStatementCollection code, IEnumerable<ClassGenerator.PropertyItemGenerator> props)
			{
				//for(var item = ReadItem(...); item != null; item = ReadItem(...)) {
				return code.For("item", ItemMethod,
					"item".AsVariable().IsNotNull(),
					ItemMethod).Statements;
			}

			public override void Close(CodeStatementCollection code, IEnumerable<ClassGenerator.PropertyItemGenerator> props)
			{
				foreach (var prop in props)
				{
					if (!prop.IsRequiredItem) continue;
					//if (Property == null) {
					code.Condition(prop.Name.AsField().IsNull()).TrueStatements
						.Comment(prop.Name)
						//Property1 = new Item1();
						.Assign(prop.Name.AsField(), prop.RequiredItemType.CreateInvoke())
						//Property1.InitBinary();
						.MethodInvoke(prop.Name.AsField(), "InitBinary");
				}
			}

			protected override CodeStatementCollection GenerateData()
			{
				if (context.IsRepeatItem)
				{
					//if (item is Type) {
					var any = Statements.Condition(ItemCondition());
					any.TrueStatements
						.Comment(Name)
						//Property.Add((Type)item);
						.MethodInvoke(Name.AsField(), "Add", "item".AsVariable().Cast(TypeName));
					//} else {
					return any.FalseStatements;
				}
				else
				{
					//if (Property == null && item is Type) {
					var any = Statements.Condition(Name.AsField().IsNull().And(ItemCondition()));
					any.TrueStatements
						.Comment(Name)
						//Property = (Type)item;
						.Assign(context.Property, "item".AsVariable().Cast(TypeName));
					//} else {
					return any.FalseStatements;
				}
			}
		}

		//[BinBlock(ItemOrder = BinItemOrder.Sequence)]
		private sealed class PropertySequenceSampleGenerator : PropertyItemGenerator
		{
			public PropertySequenceSampleGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			public override CodeStatementCollection Init(CodeStatementCollection code, IEnumerable<ClassGenerator.PropertyItemGenerator> props)
			{
				//IBinSerializable item;
				code.Variable<IBinSerializable>("item");
				return null;
			}

			protected override CodeStatementCollection GenerateData()
			{
				ForEachSample(item =>
				{
					//item = new Item();
					//item.ReadBinary(reader);
					ResolveNextSimpleItem(Statements, context.Class.BinBlock, item.ResolveItemType() ?? TypeName);

					if (context.IsRepeatItem)
					{
						Statements
							//Property.Add((Type)item);
							.MethodInvoke(Name.AsField(), "Add", "item".AsVariable().Cast(TypeName));
					}
					else
					{
						Statements
							//Property = (Type)item;
							.Assign(context.Property, "item".AsVariable().Cast(TypeName));
					}
				});

				if (context.IsRepeatItem || !context.IsRequiredItem ||
					BinItem.LastOrDefault().Condition.IsNullEmpty()) return null;

				Statements
					.Comment(Name)
					//Property1 = new Item1();
					.Assign(context.Property, RequiredItemType.CreateInvoke())
					//Property1.InitBinary();
					.MethodInvoke(Name.AsField(), "InitBinary");
				return null;
			}
		}

		//[BinBlock(ItemOrder = BinItemOrder.Choice)]
		private sealed class PropertyChoiceSampleGenerator : PropertyItemGenerator
		{
			public PropertyChoiceSampleGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			public override CodeStatementCollection Init(CodeStatementCollection code, IEnumerable<ClassGenerator.PropertyItemGenerator> props)
			{
				//item = new Item();
				//item.ReadBinary(reader);
				ResolveNextSimpleItem(code, context.Class.BinBlock, context.Class.IEnumerableType);
				return null;
			}

			protected override CodeStatementCollection GenerateData()
			{
				ForEachSample(item =>
				{
					Statements
						//Property = (Type)item;
						.Assign(context.Property, "item".AsVariable().Cast(TypeName));
				});
				return Statements;
			}
		}

		private sealed class FormatContext : ClassGenerator.FormatContext
		{
			public FormatContext(ClassGenerator.PropertyContext context, BinDataAttribute binData)
				: base(context, binData) { }

			protected override string ResolveLengthMethod(out Type lenType)
			{
				return BinData.LengthFormat.ResolveRead(out lenType);
			}

			protected override string ResolveProcessMethod(out Type arg)
			{
				return Format.ResolveRead(out arg);
			}

			public override CodeExpression Encoder
			{
				get
				{
					if (Format.HasEncoding()) return base.Encoder;
					return "reader".AsArgument();
				}
			}
		}

		//[BinData]
		private sealed class FormatPrimitiveGenerator : ClassGenerator.FormatGenerator
		{
			public FormatPrimitiveGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements.Comment(PropertyName);
				//reader.ReadProperty();
				AssignItem(context.Encoder.MethodInvoke(context.ProcessMethod));
			}
		}

		//[BinData(LengthFormat)]
		//[BinData(LengthFormat, LengthCustomMethod)]
		private sealed class FormatLongGenerator : ClassGenerator.FormatGenerator
		{
			public FormatLongGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements.Comment(PropertyName);
				//encoder.ReadProperty((LengthType)reader.ReadLength());
				AssignItem(context.Encoder.MethodInvoke(context.ProcessMethod,
					"reader".AsArgument().MethodInvoke(context.LengthMethod).Cast(context.ProcessMethodArgument)));
			}
		}

		//[BinData(LengthCustomMethod)]
		private sealed class FormatLongCustomGenerator : ClassGenerator.FormatGenerator
		{
			public FormatLongCustomGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements.Comment(PropertyName);
				//encoder.ReadProperty((LengthType)Length);
				AssignItem(context.Encoder.MethodInvoke(context.ProcessMethod,
					LengthCustomMethod.Cast(context.ProcessMethodArgument)));
			}
		}
	}
}
